﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace Sharp.Interprocess.MemoryManager
{
    internal class SegmentDeallocator
    {
        #region Private Members

        private Mutex m_SyncObject;

        #endregion

        #region Constructor(s)

        public SegmentDeallocator(Mutex syncObject)
        {
            m_SyncObject = syncObject;
        }

        #endregion

        public void Free(SegmentHandle segmentHandle,
                         GeneralTree<SegmentBlock> segmentBlockData,
                         Dictionary<int, int> availableBlockCountBySize,
                         HashSet<long> allocatedSegmentOffsets)
        {
            try
            {
                m_SyncObject.WaitOne();
                foreach (var allocatedSegment in segmentHandle.Segments)
                {
                    FreeSegment(allocatedSegment, segmentBlockData, availableBlockCountBySize, allocatedSegmentOffsets);
                }
            }
            finally
            {
                m_SyncObject.ReleaseMutex();
            }
        }

        #region Helper Methods

        private void FreeSegment(Segment allocatedSegment,
                                 GeneralTree<SegmentBlock> segmentBlockData,
                                 Dictionary<int, int> availableBlockCountBySize,
                                 HashSet<long> allocatedSegmentOffsets)
        {
            var relevantSegmentBlockNode = segmentBlockData.FindNode(segmentBlock => segmentBlock.BlockOffset == allocatedSegment.SegmentOffset &&
                                                                                     segmentBlock.BlockSize == allocatedSegment.SegmentSize);
            if (relevantSegmentBlockNode == null)
            {
                throw new ApplicationException("Invalid allocated segment data - unable to free specified segment block");
            }

            allocatedSegmentOffsets.Remove(allocatedSegment.SegmentOffset);

            relevantSegmentBlockNode.Ancestors
                                    .ToList()
                                    .ForEach(segmentBlockNode =>
                                        {
                                            availableBlockCountBySize[segmentBlockNode.Data.BlockSize]++;
                                        });

            MarkChildNodesFree(relevantSegmentBlockNode,availableBlockCountBySize,allocatedSegmentOffsets);
        }

        private void MarkChildNodesFree(GeneralTree<SegmentBlock> segmentBlockDataToFree, Dictionary<int, int> availableBlockCountBySize, HashSet<long> allocatedSegmentOffsets)
        {
            foreach (var childNode in segmentBlockDataToFree.ChildNodes)
            {
                allocatedSegmentOffsets.Remove(childNode.Data.BlockOffset);
                availableBlockCountBySize[childNode.Data.BlockSize]++;

                MarkChildNodesFree(childNode,availableBlockCountBySize,allocatedSegmentOffsets);
            }
        }

        #endregion
    }
}
